BINDING_HEADER_KONG = "Kong Automatic UI Hider";
BINDING_NAME_KONG_ENABLE = "Enable UI Fading";
BINDING_NAME_KONG_DISABLE = "Disable UI Fading";
BINDING_NAME_KONG_TOGGLE = "Toggle UI Fading On/Off";
local VERSION = "1.2b";

Kong_Frames = {};
KONG_TRIGGER_NAMES = {"combat", "nocombat", "vehicle", "novehicle", "casting", "nocasting", "macro", "health", "power", "mouseover"};
local FadeFrames = {};
local LastFocus = nil;
local LastRoot = nil;

-- Remove these duplicate command line messages
local CURSOR_OVER_WORLD = "Mouse cursor must be over the desired frame.";
local NOT_REGISTERED = "Frame is not registered for fading (use |cFF33FF99/kong hide|r first).";
local ALREADY_REGISTERED = "Frame is already registered for fading.";
local CANNOT_SAVE = "Nameless frames cannot be saved.";
local FRAME_ADDED = "Frame registered.";
local FRAME_REMOVED = "Frame unregistered.";
local ALPHA_REQUIRED = "Alpha required (ex: |cFF33FF99/kong %s alpha .6|r)."
local BAD_ALPHA = "Alpha must be between 0 (transparent) and 1 (opaque).";
local ALPHA_SET = "Alpha set.";
local SPEED_REQUIRED = "Speed required (ex: |cFF33FF99/kong %s speed in .2|r)."
local SPEED_IN_OR_OUT = "\"in\" or \"out\" required (ex: |cFF33FF99/kong %s speed in .2|r).";
local BAD_SPEED = "Speed must be 0 or greater."
local SPEED_SET = "Speed set.";
local TRIGGER_CONDITION = {
    ["combat"] = "during combat",
    ["nocombat"] = "out of combat",
    ["vehicle"] = "when in a vehicle",
    ["novehicle"] = "when not in a vehicle",
    ["casting"] = "while casting",
    ["nocasting"] = "while not casting",
    ["macro"] =  "when the given condition is true",
    ["mouseover"] = "when moused-over",
};
local HELP_TRIGGER_COMMANDS = "|cFFBBBBFFKong Automatic UI Hider|r %s commands ( |cFF33FF99/kong %s|r ):";
local HELP_TRIGGER_SHOW = "|cFF33FF99show|r: configures the frame to fade in %s.";
local HELP_TRIGGER_HIDE = "|cFF33FF99hide|r: configures the frame to remain hidden %s.";
local HELP_TRIGGER_ALPHA = "|cFF33FF99alpha <alpha>|r: sets the alpha (transparency) to use %s.";
local HELP_TRIGGER_SPEED = "|cFF33FF99speed <in||out> <seconds>|r: sets the duration in seconds over which this trigger fades the frame in or out.";
local TRIGGER_NOT_REGISTERED = "Trigger %q has not been configured for use with this frame (use |cFF33FF99/kong %s|r first).";
local UNKNOWN_OPTION = "Unknown option: %q (For help type |cFF33FF99/kong %s|r for help).";
local BOOLEAN_SUCCESS = {
    ["show"] = "Frame will now appear %s.",
    ["hide"] = "Frame will now remain hidden %s.",
};
local HELP_MACRO_GET = "|cFF33FF99get|r: displays the current macro condition (if any).";
local HELP_MACRO_SET = "|cFF33FF99set <condition>|r: configures the frame to fade in when the given macro condition evaluates to true. "
                     .."(ie: |cFF33FF99/kong macro set [exists]|r will make the frame fade in when you have a target selected.)";
local HELP_MACRO_CLEAR = "|cFF33FF99clear|r: clears the current macro condition.";
local MACRO_APPLIED = "Frame will now be shown when the condition %q is met.";
local MACRO_REMOVED = "Macro condition cleared.";
local CURRENT_MACRO = "Current macro condition for this frame is %q.";
local NO_CURRENT_MACRO = "No current macro condition for this frame.";
local CONDITION_REQUIRED = "Macro condition required (ex: |cFF33FF99/kong macro set [mounted]|r ).";
local INVALID_CONDITION = "Invalid macro condition %q (ex: |cFF33FF99/kong macro set [mounted]|r ).";
local GROUP_NAME_NEEDED = "Group name must be specified (ex: |cFF33FF99/kong group %s group1|r)."
local GROUP_ADD = "Frame added to group: %q";
local GROUP_REMOVE = "Frame removed from group: %q";
local FRAME_ALREADY_IN_GROUP = "Frame is already a member of group: %q";
local FRAME_NOT_IN_GROUP = "Frame is not currently in group: %q";
local NO_SUCH_GROUP = "There is no group named %q.";
local HELP_GROUP_ADD = "|cFF33FF99add <name>|r: adds a frame to a named mouseover group. All frames "
                     .."in this group will be shown whenever any frame in the group is moused-over.";
local HELP_GROUP_REMOVE = "|cFF33FF99remove <name>|r: removes a frame from the specified mouseover group.";
local HELP_GROUP_LIST = "|cFF33FF99list|r: lists the groups that a frame is a member of.";
local ALPHA_IN_OR_OUT = "\"in\" or \"out\" required (ex: |cFF33FF99/kong alpha in .2|r).";
local PROFILE_NAME_REQUIRED = "Profile name required.";
local NO_SUCH_PROFILE = "Profile %q does not exist. Use |cFF33FF99/kong profile list|r to see a list of available profiles.";
local PROFILE_LOADED = "Profile %q loaded.";
local NOW_USING_PROFILE = "Now using profile %q.";
local PROFILE_ALREADY_EXISTS = "Profile %q already exists.";
local CANNOT_DELETE_CURRENT_PROFILE = "Cannot delete current profile.";
local PROFILE_ALREADY_LOADED = "Profile %q is already the current profile.";
local PROFILE_DELETED = "Profile %q deleted.";
local HELP_PROFILE_COMMANDS = "|cFFBBBBFFKong Automatic UI Hider|r profile commands ( |cFF33FF99/kong profile|r ):";
local HELP_PROFILE_LOAD = "|cFF33FF99load <name>|r: loads the specified profile.";
local HELP_PROFILE_CREATE = "|cFF33FF99create <name>|r: creates a new profile with the given name.";
local HELP_PROFILE_DELETE = "|cFF33FF99delete <name>|r: deletes the specified profile.";
local HELP_PROFILE_LIST = "|cFF33FF99list|r: lists all available profiles.";
local HELP_PROFILE_CURRENT = "|cFF33FF99current|r: prints the current profile name.";
local FRAME_LIST = "Frames registered for fading:";
local INVALID_COMMAND = " command unknown (Type |cFF33FF99/kong|r for help)."
local HELP_COMMANDS = "|cFFBBBBFFKong Automatic UI Hider|r commands ( |cFF33FF99/kong|r ):";
local HELP_HIDE = "|cFF33FF99hide|r: registers a frame to be faded out when not in use.";
local HELP_SHOW = "|cFF33FF99show|r: unregisters a frame, causing it to be permanently shown.";
local HELP_COMBAT = "|cFF33FF99combat|r: configures the way a frame behaves when in combat.";
local HELP_NOCOMBAT = "|cFF33FF99nocombat|r: configures the way a frame behaves when not in combat.";
local HELP_VEHICLE = "|cFF33FF99vehicle|r: configures the way a frame behaves when in a vehicle.";
local HELP_CASTING = "|cFF33FF99casting|r: configures the way a frame behaves when in a casting.";
local HELP_MOUSEOVER = "|cFF33FF99mouseover|r: configures the way a frame behaves when the mouse cursor is over it.";
local HELP_GROUP = "|cFF33FF99group|r: configures multiple frames to appear when a single frame is moused-over.";
local HELP_MACRO = "|cFF33FF99macro|r: configures the way a frame behaves when the result of a user-specified macro condition changes.";
local HELP_ALPHA = "|cFF33FF99alpha <in||out>|r: sets the default alpha (transparency) to use when fading a frame in or out.";
local HELP_SPEED = "|cFF33FF99speed <in||out> <seconds>|r: sets the default duration in seconds over which a frame fades in or out.";
local HELP_LIST = "|cFF33FF99list|r: lists each frame (or children of unnamed frames) currently registered for fading.";
local HELP_TOGGLE = "|cFF33FF99enable / disable / toggle|r: enables or disables fading of the user interface.";
local HELP_PROFILE = "|cFF33FF99profile|r: manages saved Kong configurations and allows for configuration sharing between characters.";

local CANNOT_FIND_FRAME = "Cannot find saved frame: ";

Kong_State = {
    ["combat"] = false,
    ["nocombat"] = true,
    ["vehicle"] = false,
    ["novehicle"] = true,
    ["casting"] = false,
    ["nocasting"] = true;
};
Kong_PowerUnits = {
"player",
"pet",
"vehicle",
"focus",
"party1",
"party2",
"party3",
"party4",
"partypet1",
"partypet2",
"partypet3",
"partypet4",
}

Kong_PowerTypes = {
"MANA",
"ENERGY",
"RAGE",
"RUNIC_POWER",
"FOCUS",
"HAPPINESS",
}

-- Contains the default trigger values
local TriggerDefaults = {
    secondsIn = .2,
    secondsOut = 1,
    alphaIn = 1,
    alphaOut = 0,
};
Kong_TriggerFactory = {
    Create = function(FrameDefaults)
        local Trigger = {};
        if FrameDefaults then
            Trigger.refs = 0;
        end
        setmetatable(Trigger, {__index = FrameDefaults or TriggerDefaults});
        return Trigger;
    end,
};

local pairs = _G.pairs;
local ipairs = _G.ipairs;

function Kong_OverrideAlpha(Root, override)

    if Kong_Frames[Root] then
        if override then 
            Root.kongDoOverrideAlphaHook = true
            Root.kongFaderAlpha = Root:GetAlpha()
            Root.kongFrameAlpha = Root:GetAlpha()
            Root.KongSetAlpha = function(frame, alpha)
                frame.kongDoOverrideAlphaHook = false
                frame.kongFaderAlpha = alpha
                frame:SetAlpha(math.min(frame.kongFrameAlpha, alpha));
                frame.kongDoOverrideAlphaHook = true
            end
            
            -- Only need to actually hook once per session
            if not Root.kongAlphaOverridden then
                hooksecurefunc(Root, "SetAlpha", function(self, alpha) 
                    if self.kongDoOverrideAlphaHook then
                        self.kongDoOverrideAlphaHook = false
                        self.kongFrameAlpha = alpha
                        self:SetAlpha(math.min(alpha, self.kongFaderAlpha))
                        self.kongDoOverrideAlphaHook = true
                    end
                end)
                Root.kongAlphaOverridden = true
            end
        else
            Root.kongDoOverrideAlphaHook = nil
            Root.kongFaderAlpha = nil
            Root.kongFrameAlpha = nil
            Root.KongSetAlpha = Root.SetAlpha
        end
    end
end

function Kong_CallOnFrameHeirarchy(Frames, funcName, ...)
    for _, Frame in pairs(Frames) do
        Frame[funcName](Frame, ...)
        Kong_CallOnFrameHeirarchy({Frame:GetChildren()}, funcName, ...)
    end
end

function Kong_DisableDraw(Frame)
    local Frames = {Frame}
    Kong_CallOnFrameHeirarchy(Frames, "DisableDrawLayer", "BACKGROUND")
    Kong_CallOnFrameHeirarchy(Frames, "DisableDrawLayer", "BORDER")
    Kong_CallOnFrameHeirarchy(Frames, "DisableDrawLayer", "ARTWORK")
    Kong_CallOnFrameHeirarchy(Frames, "DisableDrawLayer", "OVERLAY")
end

function Kong_EnableDraw(Frame) 
    local Frames = {Frame}
    Kong_CallOnFrameHeirarchy(Frames, "EnableDrawLayer", "BACKGROUND")
    Kong_CallOnFrameHeirarchy(Frames, "EnableDrawLayer", "BORDER")
    Kong_CallOnFrameHeirarchy(Frames, "EnableDrawLayer", "ARTWORK")
    Kong_CallOnFrameHeirarchy(Frames, "EnableDrawLayer", "OVERLAY")
    
end
    
function Kong_DoFade(Frame, mode, startAlpha, endAlpha, duration, finishedFunc)

    Frame.KongInfo = {
        mode = mode,
        startAlpha = startAlpha,
        endAlpha = endAlpha,
        duration = duration,
        finishedFunc = finishedFunc,
    }

    local index = 1;
    while FadeFrames[index] do
        -- If frame is already set to fade then return
        if ( FadeFrames[index] == Frame ) then
            return;
        end
        index = index + 1;
    end
    tinsert(FadeFrames, Frame);
    -- print("Fading", Frame:GetName(), mode, "from", startAlpha, "to", endAlpha, "over", duration, "seconds");
end

function Kong_FadeIn(Frame, Trigger)

    Trigger.refs = Trigger.refs + 1;
    
    if Trigger.refs == 1 then
        Kong_CalculateFadeIn(Frame);
    end
end

function Kong_CalculateFadeIn(Frame)
    local currentAlpha = ceil(Frame:KongGetAlpha()*100)/100;
    local fastestRate = 0;
    local endAlpha = 1;
    local duration;
    local finishedFunc = nil;

    -- Iterate thru all possible triggers
    for _,triggerName in pairs(KONG_TRIGGER_NAMES) do
        local Trigger = Kong_Frames[Frame][triggerName];
        -- Only triggers configured for this frame
        if Trigger then
            -- Only triggers that are currently fading in
            if Trigger.refs > 0 and Trigger.alphaIn >= currentAlpha then
            
                -- Only fade in if alpha is increasing
                if Trigger.alphaIn > currentAlpha then
                    finishedFunc = Kong_CalculateFadeIn
                end
        
                -- Find the fastest fader
                local rate = (Trigger.alphaIn - Trigger.alphaOut) / Trigger.secondsIn;
                if rate > fastestRate or (rate == fastestRate and Trigger.alphaIn > endAlpha) then
                    fastestRate = rate;
                    endAlpha = Trigger.alphaIn;
                    duration = (endAlpha - currentAlpha) / rate;
                end
            end
        end
    end
    
    if (fastestRate > 0) then
        Kong_DoFade(Frame, "IN", currentAlpha, endAlpha, duration, finishedFunc);
        
        if Kong_Frames[Frame].disableDrawWhenHidden and (currentAlpha == 0) then
            Kong_EnableDraw(Frame)
        end
    end
end

function Kong_FadeOut(Frame, Trigger)

    Trigger.refs = Trigger.refs - 1;
    
    assert(Trigger.refs >= 0, "ref < 0 for frame: "..tostring(Frame:GetName()));

    if Trigger.refs == 0 then
    
        -- Adjust fade time for the case where the trigger was still fading in
        local rate = (Trigger.alphaOut-Trigger.alphaIn)/Trigger.secondsOut;
        Trigger.lastFadeTime = GetTime() + (Trigger.alphaIn - Frame:KongGetAlpha()) / rate;

        Kong_CalculateFadeOut(Frame);
    end
end

function Kong_CalculateFadeOut(Frame)

    local currentTime = GetTime();
    local currentAlpha = Frame:KongGetAlpha();
    local highestAlpha = Kong_Frames[Frame].TriggerDefaults.alphaOut;
    local slowestRate = math.huge;
    local highestTrigger;
    local startAlpha = Kong_Frames[Frame].TriggerDefaults.alphaOut;
    local endAlpha = startAlpha;
    local duration = 0;
    local finishedFunc = nil;
    
    -- Find the fader with the highest current alpha
    for _,triggerName in pairs(KONG_TRIGGER_NAMES) do
        local Trigger = Kong_Frames[Frame][triggerName];
        if Trigger then
            if Trigger.refs > 0 then
                if Trigger.alphaIn >= currentAlpha then
                    return; -- An active trigger has higher alpha, dont need to fade out
                else
                    -- Dont fade past the highest active trigger alpha
                    endAlpha = max(endAlpha, Trigger.alphaIn);
                end
            elseif Trigger.lastFadeTime then
                local fadeAge = currentTime-Trigger.lastFadeTime;
                -- If the trigger is fading out
                if fadeAge < Trigger.secondsOut then
                    local rate = (Trigger.alphaOut-Trigger.alphaIn)/Trigger.secondsOut;
                    local currentFaderAlpha = Trigger.alphaIn + rate*fadeAge;
                    startAlpha = max(startAlpha, currentFaderAlpha);
                    -- Dont fade if there is an active trigger with higher alpha than this fader's current alpha
                    if currentFaderAlpha > endAlpha then
                        -- Use the trigger with the highest current alpha
                        if (currentFaderAlpha > highestAlpha)
                        or (currentFaderAlpha == highestAlpha and rate < slowestRate) then
                            highestAlpha = currentFaderAlpha;
                            slowestRate = rate;
                            highestTrigger = Trigger;
                        end
                    end
                end
            end
        end
    end
    startAlpha = min(startAlpha, currentAlpha);

    -- If a fader was found
    if (highestTrigger) then
        -- Find the highest point of intersection with other faders
        local rate1 = (highestTrigger.alphaOut-highestTrigger.alphaIn)/highestTrigger.secondsOut;
        local alpha1 = highestTrigger.alphaIn + rate1*(currentTime-highestTrigger.lastFadeTime);
        for _,triggerName in pairs(KONG_TRIGGER_NAMES) do
            Trigger = Kong_Frames[Frame][triggerName];
            -- Only triggers configured for this frame
            if Trigger and Trigger ~= highestTrigger then
                if Trigger.lastFadeTime then
                    local fadeAge = currentTime-Trigger.lastFadeTime;
                    if fadeAge < Trigger.secondsOut then
                        local rate2 = (Trigger.alphaOut-Trigger.alphaIn)/Trigger.secondsOut;
                        local alpha2 = Trigger.alphaIn + rate2*fadeAge;

                        local interceptTime = (alpha2-alpha1)/(rate1-rate2);
                        if interceptTime > 0 and interceptTime < highestTrigger.secondsOut
                                                 -(currentTime-highestTrigger.lastFadeTime) then
                            -- Dont fade past the intersection with a slower fader
                            local interceptAlpha = highestTrigger.alphaIn
                            + rate1*(currentTime-highestTrigger.lastFadeTime)
                            + rate1*interceptTime - .01; -- .01 extra alpha to make sure intercept is actually met
                            endAlpha = max(endAlpha, interceptAlpha);
                            finishedFunc = Kong_CalculateFadeOut;
                        end
                    end
                end
            end
        end
        duration = (endAlpha-startAlpha) / rate1;
    end

    if Kong_Frames[Frame].disableDrawWhenHidden and (endAlpha == 0) then 
        finishedFunc = Kong_DisableDraw
    end
    
    Kong_DoFade(Frame, "OUT", startAlpha, endAlpha, duration, finishedFunc)
end

function Kong_FindGlobalIndex(Frame)

    local name = Frame:GetName();
    if name then
        return "[\"" .. name .. "\"]";
    end

    for i,x in pairs(_G) do
        if x == Frame then
            if type(i) == "string" then
                return "[\"" .. i .. "\"]";
            elseif type(i) == "number" or type(i) == "boolean" then
                return "[".. tostring(i) .. "]"
            end
        end
    end
    
    for _, Child in pairs({Frame:GetChildren()}) do
        name = Kong_FindGlobalIndex(Child);
        if name then
            return name;
        end
    end
end

function Kong_Error(str, ...)
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF2222Kong Error:|r "..string.format(str, ...));
end

function Kong_Warning(str, ...)
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF9922Kong Warning:|r "..string.format(str, ...));
end

function Kong_Info(str, ...)
    DEFAULT_CHAT_FRAME:AddMessage("|cFF33FF99Kong:|r "..string.format(str, ...));
end

function Kong_RegisterFrame(Root)

    -- Associate kong with the frame
    Kong_Frames[Root] = {};

    -- Setup triggers
    Kong_Frames[Root].TriggerDefaults = Kong_TriggerFactory.Create();
    Kong_Frames[Root].combat = Kong_TriggerFactory.Create(Kong_Frames[Root].TriggerDefaults);
    Kong_Frames[Root].vehicle = Kong_TriggerFactory.Create(Kong_Frames[Root].TriggerDefaults);
    Kong_Frames[Root].mouseover = Kong_TriggerFactory.Create(Kong_Frames[Root].TriggerDefaults);

    Root.KongGetAlpha = Root.GetAlpha
    Root.KongSetAlpha = Root.SetAlpha
    Kong_FixModels(Root)
    
    -- Fade according to current state
    if Kong_Settings.enabled then
        Root:KongSetAlpha(Kong_Frames[Root].TriggerDefaults.alphaOut);
    end
    if Kong_State.combat then Kong_FadeIn(Root, Kong_Frames[Root].combat); end
    if Kong_State.vehicle then Kong_FadeIn(Root, Kong_Frames[Root].vehicle); end

    -- Attempt to save settings between sessions
    local index = Kong_FindGlobalIndex(Root);
    if index then
        Kong_Frames[Root].index = index;
    else
        Kong_Frames[Root].index = {}; -- unique grouping index for this session only
        Kong_Warning(CANNOT_SAVE);
    end
end

function Kong_UnregisterFrame(Root)

    Kong_OverrideAlpha(Root, false)
    
    local index = Kong_Frames[Root].index;
    
    -- For any groups that this frame was part of
    for groupName,Group in pairs(Kong_Settings.Groups) do 
        if Group[index] then
        
            Group[index] = nil;
        
            -- Delete the group if empty
            if not next(Group) then
                Kong_Settings.Groups[groupName] = nil;
            end
        end
    end
    Kong_Frames[Root] = nil;

    if (Kong_Settings.enabled) then
        Kong_DoFade(Root, "IN", 0, 1, .5);
    end
end

function Kong_SetFrameBooleanVisibility(Root, triggerName, visible)

    if visible and not Kong_Frames[Root][triggerName] then
            
        local Trigger = Kong_TriggerFactory.Create(Kong_Frames[Root].TriggerDefaults);

        -- Fade in if the condition is true
        if Kong_State[triggerName] then
            Kong_FadeIn(Root, Trigger);
        end

        Kong_Frames[Root][triggerName] = Trigger;
            
    elseif not visible and Kong_Frames[Root][triggerName] then
    
        -- Fade out if the Frame is already shown due to trigger
        if Kong_State[triggerName] then
        
            Kong_FadeOut(Root, Kong_Frames[Root][triggerName]);
        end

        Kong_Frames[Root][triggerName] = nil;
    end
end

function Kong_AddFrameToGroup(Root, name)

    if not Kong_Settings.Groups[name] then
        -- Create the group if it doesn't exist yet
        Kong_Settings.Groups[name] = {};
    end
    
    -- Make sure the frame isn't already in the group
    if not Kong_Settings.Groups[name][Kong_Frames[Root].index] then
    
        Kong_Settings.Groups[name][Kong_Frames[Root].index] = Root;
        return true;
    end
end

function Kong_RemoveFrameFromGroup(Root, name)

    local Group = Kong_Settings.Groups[name];
    if Group then
        if Group[Kong_Frames[Root].index] then
        
            Group[Kong_Frames[Root].index] = nil;
            if not next(Group) then
                Kong_Settings.Groups[name] = nil;
            end
            return true
        end
    end
end
  
function Kong_Enable()
    Kong_Settings.enabled = true;
    for Frame in pairs(Kong_Frames) do
        Kong_CalculateFadeOut(Frame);
    end
    KongUIFader:SetScript("OnUpdate", Kong_OnUpdate);
end

function Kong_Disable()
    Kong_Settings.enabled = false;
    for Frame in pairs(Kong_Frames) do
        if Kong_Frames[Frame].disableDrawWhenHidden and (Frame:KongGetAlpha() == 0) then
            Kong_EnableDraw(Frame)
        end
        Frame:KongSetAlpha(1);
    end
    KongUIFader:SetScript("OnUpdate", nil);
end

function Kong_SetMacro(Root, input)

    local condition = string.match(input, "(%[.*%])");
    local Trigger = Kong_Frames[Root].macro;
    
    if Trigger then

        if condition == Trigger.condition then
            return true;
        end
        
        if Trigger.lastResult then
            Kong_FadeOut(Root, Trigger);
        end
    end

    Trigger = Kong_TriggerFactory.Create(Kong_Frames[Root].TriggerDefaults);
    Trigger.condition = condition;
    
    if pcall(function() Trigger.lastResult = SecureCmdOptionParse(Trigger.condition) end) then

        Kong_Frames[Root].macro = Trigger;

        if Trigger.lastResult then
            Kong_FadeIn(Root, Trigger);
        end
        
        return true;
    else
        Kong_Frames[Root].macro = nil;
    end
end

function Kong_ClearMacro(Root)
    
    local Trigger = Kong_Frames[Root].macro;
    if Trigger and Trigger.lastResult then
        Kong_FadeOut(Root, Trigger);
    end

    Kong_Frames[Root].macro = nil;
end

function Kong_SetPowerVisibility(Root, power, Units, lessThan, threshold, Types)
    
    Kong_ClearPowerVisibility(Root, power)
    
    Trigger = Kong_TriggerFactory.Create(Kong_Frames[Root].TriggerDefaults);
    Trigger.Units = Units
    Trigger.Types = Types
    Trigger.lessThan = lessThan
    Trigger.threshold = threshold
    
    Kong_Frames[Root][power] = Trigger
      
    for unit in pairs(Units) do
        Kong_PowerUpdate(power, unit)
    end 
end

function Kong_ClearPowerVisibility(Root, power)

    local Trigger = Kong_Frames[Root][power];
    if Trigger then
        for unit, active in pairs(Trigger.Units) do
            if active then
                Trigger.Units[unit] = false
                Kong_FadeOut(Root, Trigger);
            end
        end
    end
    Kong_Frames[Root][power] = nil;
end

function Kong_RenameFrame(Root, path)

    -- Update groups containing the frame
    for _,Group in pairs(Kong_Settings.Groups) do
        if Group[Kong_Frames[Root].index] then
            Group[Kong_Frames[Root].index] = nil
            Group[path] = Root
        end
    end
    
    -- Update references to frame name
    Kong_Frames[Root].index = path
end

function Kong_BooleanUpdate(name, value)

    for Frame,Kong in pairs(Kong_Frames) do
        local Trigger = Kong[name];
        if Trigger then
            if value and (not Kong_State[name]) then
                Kong_FadeIn(Frame, Trigger);
            elseif (not value) and Kong_State[name] then
                Kong_FadeOut(Frame, Trigger);
            end
        end
        Trigger = Kong["no"..name];
        if Trigger then
            if value and (not Kong_State[name]) then
                Kong_FadeOut(Frame, Trigger);
            elseif (not value) and Kong_State[name] then
                Kong_FadeIn(Frame, Trigger);
            end
        end
    end
    Kong_State[name] = value;
    Kong_State["no"..name] = not value;
end

function Kong_PowerUpdate(power, unit, powerType)

    local GetPower, GetPowerMax;
    if "health" == power then
        GetPower, GetPowerMax = UnitHealth, UnitHealthMax
    else
        GetPower, GetPowerMax = UnitPower, UnitPowerMax
    end
    
    for Root, Kong in pairs(Kong_Frames) do
        local Trigger = Kong[power]
        if Trigger and (nil ~= Trigger.Units[unit]) then
            local active = false
            if (not powerType) or Trigger.Types[powerType] then
                if Trigger.lessThan then
                    active = GetPower(unit)/GetPowerMax(unit) < Trigger.threshold
                else
                    active = GetPower(unit)/GetPowerMax(unit) > Trigger.threshold
                end
            end
        
--print(unit, power, ":", GetPower(unit), "/", GetPowerMax(unit), "=", active, Kong.index, Trigger.Units[unit])
            if active and not Trigger.Units[unit] then
                Trigger.Units[unit] = true
                Kong_FadeIn(Root, Trigger)
            elseif not active and Trigger.Units[unit] then
                Trigger.Units[unit] = false
                Kong_FadeOut(Root, Trigger)
            end
        end
    end
end

function Kong_LoadSettings()

    -- Only need to initialize once at start and for new profiles
    if Kong_Initialized then
        return;
    end
    Kong_Initialized = true;
    
    Kong_Frames = {};
    FadeFrames = {};
    LastFocus = nil;
    LastRoot = nil;

    if not Kong_Profiles then
        Kong_Profiles = {};
    end
    
    -- Replace spaces in profiles created prior to v0.9c
    local TempProfiles = {};
    for n, p in pairs(Kong_Profiles) do 
        if string.find(n, " ") then
            TempProfiles[string.gsub(n," ","_")] = p;
            Kong_Profiles[n] = nil;
        end
    end
    for n, p in pairs(TempProfiles) do
        Kong_Profiles[n] = TempProfiles[n];
    end
    
    local defaultProfile = string.lower(UnitName("player")).."_of_"..string.lower(string.gsub(GetRealmName()," ","_"));
    local enabled = true;
    
        -- Convert from previous version
    if Kong_Settings and Kong_Settings.version then
    
        local version = gsub(Kong_Settings.version,"%a","")
        version = tonumber(version);
        
        if version >= 0.7 and version < 0.9 then
            if not Kong_Settings.Groups then
                Kong_Settings.Groups = {};
            end
            
            -- TriggerDefaults.refs was being saved in version 0.7
            for _,Frame in pairs(Kong_Settings.Frames) do
                Frame.TriggerDefaults.refs = nil;
            end
            
            -- Convert current settings to a profile
            enabled = Kong_Settings.enabled;
            Kong_Settings.enabled = nil;
            Kong_Settings.version = VERSION;
            Kong_Profiles[defaultProfile] = Kong_Settings;
        end
    end
    
    if (not Kong_Settings) or (not Kong_Settings.profile) then
        Kong_Settings = {
            profile = defaultProfile,
            enabled = enabled,
        };
    end
    Kong_Settings.profile = string.gsub(Kong_Settings.profile," ","_");
        
    -- If creating a new profiles
    if not Kong_Profiles[Kong_Settings.profile] then
        Kong_Profiles[Kong_Settings.profile] = {
            Frames = {},
            Groups = {},
        };
    end
    Kong_Profiles[Kong_Settings.profile].version = VERSION
    
    -- Kong_Settings will have all the same fields as the profile
    setmetatable(Kong_Settings, {__index = Kong_Profiles[Kong_Settings.profile]});
    
    -- Index frames by number (for pre-v1.1 compatibility)
    local tempFrames = Kong_Settings.Frames;
    Kong_Profiles[Kong_Settings.profile].Frames = {}
    for _, SavedFrame in pairs(tempFrames) do
        table.insert(Kong_Settings.Frames, SavedFrame)
    end
    
    -- Initialize group-frames to 0 (for pre-v1.1 compatibility)
    for _,Group in pairs(Kong_Settings.Groups) do
        for index in pairs(Group) do
            Group[index] = 0;
        end
    end
    
    -- Convert pre-v1.2 indexes
    for _, SavedFrame in pairs(Kong_Settings.Frames) do
        if string.sub(SavedFrame.index, 1, 1) ~= "[" then
            SavedFrame.index = gsub(SavedFrame.index, "%.", "[\"")
            SavedFrame.index = gsub(SavedFrame.index, "%[", "\"][")
            SavedFrame.index = gsub("[\""..SavedFrame.index.."\"]", "%]\"%]", "]")
        end
    end
    for groupName, Group in pairs(Kong_Settings.Groups) do
        local tempGroup = Group
        Kong_Settings.Groups[groupName] = {}
        for index in pairs(tempGroup) do
            if string.sub(index, 1, 1) ~= "[" then
                index = gsub(index, "%.", "[\"")
                index = gsub(index, "%[", "\"][")
                index = gsub("[\""..index.."\"]", "%]\"%]", "]")
            end
            Kong_Settings.Groups[groupName][index] = 0;
        end
    end
    
    
    Kong_LoadFrames()
end

function Kong_LoadFrames()

    for i=#Kong_Settings.Frames,1,-1 do

        local SavedFrame = Kong_Settings.Frames[i]
        
        
        -- Convert stored path to frame
        if not SavedFrame.loader then
            SavedFrame.loader = loadstring("return _G"..SavedFrame.index)
        end
        
        local Frame = nil;
        pcall(function() Frame = SavedFrame.loader(); end)
        
        if Frame then

            local Root = Kong_GetRoot(Frame);
            if not Kong_Frames[Root] then
                
                table.remove(Kong_Settings.Frames, i)
                Kong_Frames[Root] = SavedFrame;
                
                setmetatable(SavedFrame.TriggerDefaults, {__index = TriggerDefaults});
                
                Root.KongGetAlpha = Root.GetAlpha
                Root.KongSetAlpha = Root.SetAlpha
                if SavedFrame.overrideAlpha then
                    Kong_OverrideAlpha(Root, true)
                end
                Kong_FixModels(Root)
                
                -- Initialize each trigger with defaults
                for _,triggerName in pairs(KONG_TRIGGER_NAMES) do
                    local Trigger = SavedFrame[triggerName];
                    if Trigger then
                        setmetatable(Trigger, {__index = SavedFrame.TriggerDefaults});
                        Trigger.refs = 0;
                    end
                end
                
                if Kong_Settings.enabled then
                    Kong_CalculateFadeOut(Root);
                end
                
                -- Fade frame according to current state
                for _,triggerName in pairs(KONG_TRIGGER_NAMES) do
                    local Trigger = SavedFrame[triggerName];
                    if Trigger then
                        if triggerName == "health" or triggerName == "power" then
                            for unit in pairs(Trigger.Units) do
                                Kong_PowerUpdate(triggerName, unit)
                            end
                        elseif Kong_State[triggerName] then
                            Kong_FadeIn(Root, Trigger);
                        end
                    end
                end
                
                -- Connect the mouseover group list to actual frames
                for groupName,Group in pairs(Kong_Settings.Groups) do
                    if Group[SavedFrame.index] then
                    
                        -- Fade in the frame if the group is active
                        for i=1,Group[SavedFrame.index] do
                            Kong_FadeIn(Root, SavedFrame.mouseover);
                        end
                        
                        Group[SavedFrame.index] = Root;
                    end
                end
            end
        end
    end
end

function Kong_Unload()

    for Root, Settings in pairs(Kong_Frames) do

        if Kong_Frames[Root].disableDrawWhenHidden and (Root:KongGetAlpha() == 0) then
            Kong_EnableDraw(Root)
        end
        
        Root:KongSetAlpha(1);
        
        Kong_OverrideAlpha(Root, false)
        Root.KongGetAlpha = nil
        Root.KongSetAlpha = nil
        
        
        -- Remove runtime-specific data
        for _,triggerName in pairs(KONG_TRIGGER_NAMES) do
            if Settings[triggerName] then
                Settings[triggerName].refs = nil;
                Settings[triggerName].lastFadeTime = nil;
                Settings[triggerName].lastResult = nil;
                if triggerName == "health" or triggerName == "power" then
                    for unit in pairs(Settings[triggerName].Units) do
                        Settings[triggerName].Units[unit] = false
                    end
                end
            end
        end
        Settings.refs = nil;
        Settings.loader = nil
        
        -- Move it back to Kong_Settings to be saved
        Kong_Frames[Root] = nil;
        if type(Settings.index) ~= "table" then
            table.insert(Kong_Settings.Frames, Settings);
        end
    end
    
    -- "disconnect" the groups from the actual frames
    for _,Group in pairs(Kong_Settings.Groups) do
        for index in pairs(Group) do
            Group[index] = 0
        end
    end
    
    -- In case a new profile is about to be loaded
    Kong_Initialized = false;
end

local FRAME_FIND_PERIOD = 1.0
local MACRO_UPDATE_PERIOD = 0.1;
local frameFindTimer = 0
local macroTimer = 0;
function Kong_OnUpdate(self, elapsed)

    frameFindTimer = frameFindTimer - elapsed
    if frameFindTimer <= 0 then
        frameFindTimer = FRAME_FIND_PERIOD
        
        -- Load any missing frames
        Kong_LoadFrames()
    end

    macroTimer = macroTimer - elapsed;
    if macroTimer <= 0 then
        macroTimer = MACRO_UPDATE_PERIOD;
        -- Did any macro conditions change?
        for Frame, Kong in pairs(Kong_Frames) do
            local Trigger = Kong.macro;
            if Trigger then
                result = SecureCmdOptionParse(Trigger.condition);
                if result and not Trigger.lastResult then
                    Kong_FadeIn(Frame, Trigger);
                elseif not result and Trigger.lastResult then
                    Kong_FadeOut(Frame, Trigger);
                end
                Trigger.lastResult = result;
            end
        end
    end
    
    -- Did the mouse focus change?
    local Focus = GetMouseFocus();
    if Focus ~= LastFocus then
    
        -- Store the focus
        LastFocus = Focus;

        -- Did the mouseover frame root change?
        local FocusRoot = Kong_GetRoot(Focus);
        if LastRoot ~= FocusRoot then
        
            -- If the new mouseover root is registered with Kong
            if FocusRoot and Kong_Frames[FocusRoot] then
                -- Fade in the mouseover frame group(s) that it's a member of
                for _,Group in pairs(Kong_Settings.Groups) do
                    if Group[Kong_Frames[FocusRoot].index] then
                        for index,GroupFrame in pairs(Group) do
                            if type(GroupFrame) == "table" then
                                Kong_FadeIn(GroupFrame, Kong_Frames[GroupFrame].mouseover);
                            else
                                Group[index] = GroupFrame + 1
                            end
                        end
                    end
                end
                Kong_FadeIn(FocusRoot, Kong_Frames[FocusRoot].mouseover);
            end

            -- If the old mouseover root is registered with Kong
            if LastRoot and Kong_Frames[LastRoot] then
                -- Fade out the mouseover frame group(s) that it's a member of
                for _,Group in pairs(Kong_Settings.Groups) do
                    if Group[Kong_Frames[LastRoot].index] then
                        for index,GroupFrame in pairs(Group) do
                            if type(GroupFrame) == "table" then
                                Kong_FadeOut(GroupFrame, Kong_Frames[GroupFrame].mouseover);
                            else
                                Group[index] = GroupFrame - 1
                            end
                        end
                    end
                end
                Kong_FadeOut(LastRoot, Kong_Frames[LastRoot].mouseover);
            end

            LastRoot = FocusRoot;
        end
    end

    -- Perform actual fading
    local index = 1;
    while FadeFrames[index] do
        local frame = FadeFrames[index];
        local KongInfo = FadeFrames[index].KongInfo;
        -- Reset the timer if there isn't one, this is just an internal counter
        if ( not KongInfo.fadeTimer ) then
            KongInfo.fadeTimer = 0;
        end
        KongInfo.fadeTimer = KongInfo.fadeTimer + elapsed;

        -- If the fadeTimer is less then the desired fade time then set the alpha otherwise hold the
        -- fade state, call the finished function, or just finish the fade
        if ( KongInfo.fadeTimer < KongInfo.duration ) then
            if ( KongInfo.mode == "IN" ) then
                frame:KongSetAlpha((KongInfo.fadeTimer / KongInfo.duration)
                    * (KongInfo.endAlpha - KongInfo.startAlpha) + KongInfo.startAlpha);
            elseif ( KongInfo.mode == "OUT" ) then
                frame:KongSetAlpha(((KongInfo.duration - KongInfo.fadeTimer) / KongInfo.duration)
                    * (KongInfo.startAlpha - KongInfo.endAlpha)  + KongInfo.endAlpha);
            end
        else
            frame:KongSetAlpha(KongInfo.endAlpha);

            -- If there is a fadeHoldTime then wait until its passed to continue on
            if ( KongInfo.fadeHoldTime and KongInfo.fadeHoldTime > 0  ) then
                KongInfo.fadeHoldTime = KongInfo.fadeHoldTime - elapsed;
            else
                -- Complete the fade and call the finished function if there is one
                tDeleteItem(FadeFrames, frame);
                if ( KongInfo.finishedFunc ) then
                    KongInfo.finishedFunc(frame);
                    KongInfo.finishedFunc = nil;
                end
            end
        end

        index = index + 1;
    end
end

function Kong_OnEvent(self, event, arg1, ...)

    if event == "PLAYER_REGEN_DISABLED" then
        Kong_BooleanUpdate("combat", true);
    elseif event == "PLAYER_REGEN_ENABLED" then
        Kong_BooleanUpdate("combat", false);
    elseif event == "UNIT_ENTERED_VEHICLE" then
        if (arg1 == "player") then
            Kong_BooleanUpdate("vehicle", true);
            Kong_PowerUpdate("health", "vehicle")
            -- Kong_PowerUpdate("power", "vehicle")
        end
    elseif event == "UNIT_EXITED_VEHICLE" then
        if (arg1 == "player") then
            Kong_BooleanUpdate("vehicle", false);
            Kong_PowerUpdate("health", "vehicle")
            -- Kong_PowerUpdate("power", "vehicle")
        end
    elseif event == "UNIT_SPELLCAST_START" 
        or event == "UNIT_SPELLCAST_CHANNEL_START" then
        if (arg1 == "player") then
            Kong_BooleanUpdate("casting", true);
        end
    elseif event == "UNIT_SPELLCAST_STOP" 
        or event == "UNIT_SPELLCAST_CHANNEL_STOP" then
        if (arg1 == "player") then
            Kong_BooleanUpdate("casting", false);
        end
    elseif event == "UNIT_HEALTH"
        or event == "UNIT_MAXHEALTH" then
        Kong_PowerUpdate("health", arg1)
    elseif event == "UNIT_POWER"
        or event == "UNIT_MAXPOWER" then
        -- Kong_PowerUpdate("power", arg1)
    elseif event == "PLAYER_FOCUS_CHANGED" then
        Kong_PowerUpdate("health", "focus")
        -- Kong_PowerUpdate("power", "focus")
    elseif event == "PARTY_MEMBERS_CHANGED" then
        for i=1,4 do 
            Kong_PowerUpdate("health", "party"..i)
            -- Kong_PowerUpdate("power", "party"..i)
        end
    elseif event == "UNIT_PET" then
        local pet, matches1, matches2
        pet, matches1 = string.gsub(arg1, "player", "pet")
        pet, matches2 = string.gsub(pet, "party", "partypet")
        if matches1 + matches2 > 0 then
            Kong_PowerUpdate("health", pet)
            -- Kong_PowerUpdate("power", pet)
        end
    elseif event == "PLAYER_LOGOUT" then
        Kong_Unload();
    elseif event == "PLAYER_LOGIN" then
        Kong_LoadSettings();
        hooksecurefunc("CreateFrame", function() frameFindTimer = 0; end)
        if Kong_Settings.enabled then
            KongUIFader:SetScript("OnUpdate", Kong_OnUpdate);
        end
    end
    
    -- Need to check macro state
    macroTimer = 0;
end

-- Fix for minimap fading
hooksecurefunc(MinimapCluster, "SetAlpha", function(self, alpha) 
    if alpha == 0 then
        Minimap:Hide();
    else
        Minimap:Show();
        if alpha == 1 then
            local zoom = Minimap:GetZoom();
            Minimap:SetZoom(mod(zoom+1,6));
            Minimap:SetZoom(zoom);
        end
    end
end)

-- Fix for models ignoring alpha
function Kong_ModelAlphaFix(self)
   local alpha = self:GetAlpha();
   self:SetAlpha((alpha+.5)%1)
   self:SetAlpha(alpha);
end

Kong_BuggyModelMethods = {
   "SetModel",
   "SetUnit",
   "RefreshUnit",
}

function Kong_FixModels(Frame)
    if Frame:IsObjectType("PlayerModel") then
        for _, method in pairs(Kong_BuggyModelMethods) do 
           hooksecurefunc(Frame, method, Kong_ModelAlphaFix)
        end
    end
    for _, Child in pairs({Frame:GetChildren()}) do
        Kong_FixModels(Child)
    end
end

-- The event receiver frame
KongUIFader = CreateFrame("Frame");
KongUIFader:SetScript("OnEvent", Kong_OnEvent);

KongUIFader:RegisterEvent("PLAYER_REGEN_DISABLED");
KongUIFader:RegisterEvent("PLAYER_REGEN_ENABLED");
KongUIFader:RegisterEvent("UNIT_ENTERED_VEHICLE");
KongUIFader:RegisterEvent("UNIT_EXITED_VEHICLE");
KongUIFader:RegisterEvent("UNIT_SPELLCAST_START");
KongUIFader:RegisterEvent("UNIT_SPELLCAST_STOP");
KongUIFader:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START");
KongUIFader:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP");
KongUIFader:RegisterEvent("UNIT_HEALTH");
KongUIFader:RegisterEvent("UNIT_MAXHEALTH");
KongUIFader:RegisterEvent("UNIT_POWER");
KongUIFader:RegisterEvent("UNIT_MAXPOWER");
KongUIFader:RegisterEvent("PLAYER_FOCUS_CHANGED");
KongUIFader:RegisterEvent("PARTY_MEMBERS_CHANGED");
KongUIFader:RegisterEvent("UNIT_PET");
KongUIFader:RegisterEvent("PLAYER_LOGOUT");
KongUIFader:RegisterEvent("PLAYER_LOGIN");

-- Events that trigger early updates
KongUIFader:RegisterEvent("PLAYER_TARGET_CHANGED");
KongUIFader:RegisterEvent("MODIFIER_STATE_CHANGED");
KongUIFader:RegisterEvent("ACTIONBAR_PAGE_CHANGED");
KongUIFader:RegisterEvent("UPDATE_BONUS_ACTIONBAR");
KongUIFader:RegisterEvent("PLAYER_ENTERING_WORLD");
KongUIFader:RegisterEvent("UPDATE_SHAPESHIFT_FORM");
KongUIFader:RegisterEvent("UPDATE_STEALTH");
KongUIFader:RegisterEvent("RAID_ROSTER_UPDATE");
